
package org.crepi22.finecrop;

import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;

/**
 * @author pierre Controller for the behaviour of the application.
 */
public class Controller implements Continuation {
    static class FileAction {
        File file;
        FCAction action;
        FileAction history;

        FileAction(File file, FCAction action, FileAction history) {
            this.file = file;
            this.action = action;
            this.history = history;
        }

        public boolean equals(Object o) {
            if (!(o instanceof FileAction))
                return false;
            FileAction other = (FileAction) o;
            return file.equals(other.file) && action.equals(other.action);
        }

        public int historySize() {
            FileAction cursor = this;
            int size = 0;
            while (cursor.history != null) { size++; cursor = cursor.history; }
            return size;
        }
        
        public File origin() {
            FileAction cursor = this;
            while (cursor.history != null) cursor = cursor.history;
            return cursor.file;
        }
    }

    class QuickRotation extends AbstractAction {
        private static final long serialVersionUID = 1L;
        private final int angle;
        
        QuickRotation(String name, String path, int angle) {
            super(name, makeIcon(path));
            this.angle = angle;
            
        }
        @Override
        public void actionPerformed(ActionEvent arg0) {
            File file = step.file;
            currentAction = step.action;
            step = step.history;
            try {
                new Rotate(Controller.this, new Photo(file), angle).start();
            } catch (IOException e) {
                Controller.this.error(Messages.getString("Controller.image_error") + file + ": " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }        
    }
    
    final private Action rotateAction = new AbstractAction(Messages.getString("Controller.Rotation_title"), makeIcon("rotate.png") ) {         //$NON-NLS-1$ //$NON-NLS-2$
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent arg0) {
            File file = step.file;
            currentAction = step.action;
            step = step.history;
            photoView.setPhoto(file, FCAction.ROTATE);
        }
    };
    
    final private Action cropAction = new AbstractAction(Messages.getString("Controller.Crop_title"), makeIcon("crop.png") ) {         //$NON-NLS-1$ //$NON-NLS-2$
        private static final long serialVersionUID = 1L;
        
        @Override
        public void actionPerformed(ActionEvent arg0) {
            File file = step.file;
            currentAction = step.action;
            step = step.history;
            photoView.setPhoto(file, FCAction.CROP);
        }
    };
    
    final private Action multiAction = new AbstractAction(Messages.getString("Controller.Multiple_title"), makeIcon("multi.png") ) {         //$NON-NLS-1$ //$NON-NLS-2$
        private static final long serialVersionUID = 1L;
        
        @Override
        public void actionPerformed(ActionEvent arg0) {
            File file = step.file;
            currentAction = step.action;
            step = step.history;
            photoView.setPhoto(file, FCAction.MULTICROP);
        }
    };

    final private Action settings = new AbstractAction(Messages.getString("Controller.Settings_Title"), makeIcon("settings.png") ) {         //$NON-NLS-1$ //$NON-NLS-2$
        private static final long serialVersionUID = 1L;

        @Override
        public void actionPerformed(ActionEvent arg0) {

                JFrame options = new ConfigPanel();
                options.setVisible(true);
        }
    }; 
    final private Action rot90 = new QuickRotation(Messages.getString("Controller.Rot_Left_Title"), "left.png", 90); //$NON-NLS-1$ //$NON-NLS-2$
    final private Action rot270 = new QuickRotation(Messages.getString("Controller.Rot_Right_Title"), "right.png", 270); //$NON-NLS-1$ //$NON-NLS-2$
    final private Action rot180 = new QuickRotation(Messages.getString("Controller.Upside_Down_Title"), "upsideDown.png", 180); //$NON-NLS-1$ //$NON-NLS-2$

    final private PhotoView photoView;
    final private StateLine stateLine;
    final private List<File> allGenerated = new ArrayList<File>();
    final private JFrame frame;

    private Stack<FileAction> todo = new Stack<FileAction>();
    private FCAction currentAction;
    private FileAction step = null;

    /**
     * States of the system.
     */
    final public static String STATES[] = {
            Messages.getString("Controller.Rotation_title"), //$NON-NLS-1$
            Messages.getString("Controller.Crop_title"), //$NON-NLS-1$
            Messages.getString("Controller.Multiple_title"), //$NON-NLS-1$
            Messages.getString("Controller.Confirm_title") //$NON-NLS-1$
    };

    /**
     * What is written on the tooltips
     */
    final public static String[] TOOLTIPS = {
        Messages.getString("Controller.Rotation_tooltip"), //$NON-NLS-1$

        Messages.getString("Controller.Crop_tooltip"), //$NON-NLS-1$

        Messages.getString("Controller.Multiple_tooltip"), //$NON-NLS-1$

        Messages.getString("Controller.Confirm_tooltip") //$NON-NLS-1$
    };

    private TranslucentGlassPane glassPane;
    private Map<File, List<File>> finalResults = new HashMap<File, List<File>>();

    Controller(JFrame frame, PhotoView view, StateLine stateLine, JToolBar toolbar) {
        this.frame = frame;
        this.photoView = view;
        this.stateLine = stateLine;
        toolbar.add(settings);
        toolbar.add(rotateAction);
        toolbar.add(cropAction);
        toolbar.add(multiAction);
        toolbar.add(rot90);
        toolbar.add(rot270);
        toolbar.add(rot180);
        view.setContinuation(this);
        glassPane = new TranslucentGlassPane();
        frame.setGlassPane(glassPane);
        glassPane.setEnabled(false);
        glassPane.setVisible(true);
    }

    public void showEffect(final boolean b) {
        try {
         SwingUtilities.invokeAndWait(new Runnable() { 
             @Override
             public void run() { photoView.setEnabled(!b); glassPane.setEnabled(b); }
         });
        } catch (InterruptedException e) {
        } catch (InvocationTargetException e) {
        }
         
     }

    /*
     * (non-Javadoc)
     * @see org.crepi22.finecrop.Continuation#runFile(java.io.File)
     */
    @Override
    public void runFile(File file) {
        if (currentAction != null)
            todo.push(new FileAction(file, currentAction, step));
        else {
            File origin = step.origin();
            List<File> results = getResults(origin);
            results.add(file);
        }
        doNext();
    }

    /*
     * (non-Javadoc)
     * @see org.crepi22.finecrop.Continuation#runFiles(java.util.List)
     */
    @Override
    public void runFiles(List<File> files) {
        allGenerated.addAll(files);
        if (currentAction != null) {
            for (File file : files) {
                System.out.println(file);
                todo.push(new FileAction(file, currentAction, step));
            }
        } else {
            File origin = step.origin();
            List<File> results = getResults(origin);
            results.addAll(files);
        }
        doNext();
    }

    /**
     * Main entry point to perform a list of operations on a file.
     *
     * @param files the files
     * @param mode the mode the list of operations to perform. This can generate a tree of sub files.
     */
    
    public void perform(List<File> files, FCAction mode) {
        for (File file : files) {
            todo.push(new FileAction(file, mode, null));
        }
        doNext();
    }

    private List<File> getResults(File origin) {
        List<File> results = finalResults.get(origin);
        if (results == null) {
            results = new ArrayList<File>();
            finalResults.put(origin, results);
        }
        return results;
    }

    private void normalizeFilenames() {
        for(Map.Entry<File, List<File>> fileEntry : finalResults.entrySet()) {
            File originFile = fileEntry.getKey();
            List<File> generatedList = fileEntry.getValue();
            File bakFile = FileUtil.bakFile(originFile);
            originFile.renameTo(bakFile);
            try {
                if (generatedList.size() == 1) {
                    File generated = generatedList.get(0);
                    if (generated.equals(originFile)) generated = bakFile;
                    FileUtil.copyFile(generated, originFile);
                } else {
                    int index = 0;
                    for (File generated : generatedList) {
                        if (generated.equals(originFile)) generated = bakFile;
                        File versioned;
                        do {
                            versioned = FileUtil.versionFile(originFile, index++);
                        } while (versioned.exists());
                        FileUtil.copyFile(generated, versioned);
                    }
                }
            } catch (IOException e) {

            }
        }
    }
    
    private void doNext() {
        if (todo.isEmpty()) {
            normalizeFilenames();
            for(File generated: allGenerated) generated.delete();
            frame.dispose();
        } else {
            step = todo.pop();
            stateLine.setState(step.historySize());
            FCAction action = step.action;
            File file = step.file;
            currentAction = action.continuation;
            photoView.setPhoto(file, action.kind);
        }
    }

    @Override
    public void cancel() {
        FileAction history = step.history;
        if (history != null) {
            
            // here we remove all the tasks with the same history step 
            while(!todo.empty() && todo.peek().history == history) todo.pop();
            // we push back this history on the stack
            todo.push(history);
        } else {
            int option = JOptionPane
                    .showConfirmDialog(
                            frame,
                            Messages.getString("Controller.Cancel_question"), //$NON-NLS-1$
                            Messages.getString("Controller.Cancel_title"), JOptionPane.YES_NO_OPTION, //$NON-NLS-1$
                            JOptionPane.QUESTION_MESSAGE);
            if (option != JOptionPane.YES_OPTION) {
                todo.push(step);
            }
        }
        doNext();
    }

    /**
     * Sets the current action.
     *
     * @param currentAction the currentAction to set
     */
    public void setCurrentAction(FCAction currentAction) {
        this.currentAction = currentAction;
    }

    @Override
    public void error(String error) {
        JOptionPane.showConfirmDialog(frame, error, Messages.getString("Controller.Error_title"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$
        if (step.history !=null) todo.push(step.history);
        doNext();
    }

    private ImageIcon makeIcon(String name) {
        URL url = getClass().getResource("images/" + name); //$NON-NLS-1$
        return new ImageIcon(url);
    }
    
}
